// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;

namespace SnapObjects.Expressions
{
    internal class ObjectOperator
    {
        private static readonly TypeValueSynchronizer _synchronizer;

        static ObjectOperator()
        {
            _synchronizer = new TypeValueSynchronizer();
        }

        public static TypeValuePair Add(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x + (dynamic)y;

            return Worker(left, leftType, right, rightType, func);
        }

        public static TypeValuePair Subtract(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x - (dynamic)y;

            return Worker(left, leftType, right, rightType, func);
        }

        public static TypeValuePair Multiply(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x * (dynamic)y;

            return Worker(left, leftType, right, rightType, func);
        }

        public static TypeValuePair Division(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x / (dynamic)y;

            return Worker(left, leftType, right, rightType, func);
        }

        public static TypeValuePair Modulus(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x % (dynamic)y;

            return Worker(left, leftType, right, rightType, func);
        }

        public static TypeValuePair Caret(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => Math.Pow((dynamic)x, (dynamic)y);

            return Worker(left, leftType, right, rightType, func);
        }

        public static TypeValuePair OrElse(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x || (dynamic)y;

            var value = Worker(left, leftType, right, rightType, func);

            return new TypeValuePair(value.Value, typeof(bool));
        }

        public static TypeValuePair AndAlso(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x && (dynamic)y;

            var value = Worker(left, leftType, right, rightType, func);

            return new TypeValuePair(value.Value, typeof(bool));
        }

        public static TypeValuePair And(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x & (dynamic)y;

            return Worker(left, leftType, right, rightType, func);
        }

        public static TypeValuePair Or(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x | (dynamic)y;

            return Worker(left, leftType, right, rightType, func);
        }

        public static TypeValuePair Equal(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x == (dynamic)y;

            var value = Worker(left, leftType, right, rightType, func);

            return new TypeValuePair(value.Value, typeof(bool));
        }

        public static TypeValuePair NotEqual(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x != (dynamic)y;

            var value = Worker(left, leftType, right, rightType, func);

            return new TypeValuePair(value.Value, typeof(bool));
        }

        public static TypeValuePair LessThan(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x < (dynamic)y;

            var value = StringWorker(left, leftType, right, rightType, x => x < 0, func);

            return new TypeValuePair(value, typeof(bool));
        }

        public static TypeValuePair GreaterThan(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x > (dynamic)y;

            var value = StringWorker(left, leftType, right, rightType, x => x > 0, func);

            return new TypeValuePair(value, typeof(bool));
        }

        public static TypeValuePair LessThanOrEqual(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x <= (dynamic)y;

            var value = StringWorker(left, leftType, right, rightType, x => x <= 0, func);

            return new TypeValuePair(value, typeof(bool));
        }

        public static TypeValuePair GreaterThanOrEqual(object left, Type leftType, object right, Type rightType)
        {
            object func(object x, object y) => (dynamic)x >= (dynamic)y;

            var value = StringWorker(left, leftType, right, rightType, x => x >= 0, func);

            return new TypeValuePair(value, typeof(bool));
        }

        private static TypeValuePair Worker(
            object left,
            Type leftType,
            object right,
            Type rightType,
            Func<object, object, object> func)
        {
            var leftNode = new TypeValuePair(left, leftType);

            var rightNode = new TypeValuePair(right, rightType);

            _synchronizer.SynchronizeType(ref leftNode, ref rightNode);

            var value = func(leftNode.Value, rightNode.Value);

            return new TypeValuePair(value, leftNode.Type);
        }

        private static object StringWorker(
            object left,
            Type leftType,
            object right,
            Type rightType,
            Func<int, bool> comparer,
            Func<object, object, object> func)
        {
            var leftNode = new TypeValuePair(left, leftType);

            var rightNode = new TypeValuePair(right, rightType);

            if (!tryHandle(leftNode, rightNode, out var strResult))
            {
                _synchronizer.SynchronizeType(ref leftNode, ref rightNode);

                if (!tryHandle(leftNode, rightNode, out strResult))
                {
                    return func(leftNode.Value, rightNode.Value);
                }
            }

            return strResult;

            bool tryHandle(TypeValuePair leftPair, TypeValuePair rightPair, out object value)
            {
                if ((leftPair.Type == typeof(string) || leftPair.Value is string) &&
                    (rightPair.Type == typeof(string) || rightPair.Value is string))
                {
                    var leftValue = leftPair.Value;

                    var rightValue = rightPair.Value;

                    if (leftPair.Type != typeof(string) && !(leftValue is string))
                    {
                        leftValue = leftValue?.ToString();
                    }

                    if (rightPair.Type != typeof(string) && !(rightValue is string))
                    {
                        rightValue = rightValue?.ToString();
                    }

                    var result = String.Compare((string)leftValue, (string)rightValue);

                    value = comparer(result);

                    return true;
                }

                value = null;

                return false;
            }
        }
    }
}
